/**
 * Esta clase define un hilo que atiende a una conexión
 * DCLI que se haya realizado a un proceso DRep. La
 * conección se conecta con un proceso DMon y acutualiza
 * los valores pertinentes.
 *
 * @author Colmenares Carlos (06-39380)
 * @author Tough Alex (07-41604)
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.Socket;
//import java.util.Scanner;

class HiloDRep extends Thread {

    /**
     * Socket por el cual se realiza la comunicación con
     * el cliente. Si este valor es nulo, entonces la
     * comunicación se estará realizando directa con un prompt.
     */
    private Socket socketCliente;
    
    /**
     * Directorio usado como repositorio
     */
    private File repositorio;

    /**
     * Esta variable indica si el cliente es atentido
     * por un shell, de modo que hay que hacer preguntas
     * por línea de comandos
     */
    private boolean prompt;

    /**
     * Esta inastancia se usa para escribir texto a
     * la salida que espere por respuestas
     */
    private PrintStream out;
    
    /**
     * Esta instancia se usa para leer mensajes del
     * cliente
     */
    private BufferedReader in;
    //private Scanner in;

    /**
     * Cuando esta variable es verdadera, el hilo
     * imprime información inermedia acerca de su
     * ejecución y estado.
     */
    private boolean verboso;

    /**
     * Este atributo permite recibir mensajes del proceso
     * dmon, sin embargo, es necesario bloquear el uso
     * de este recurso al comunicarse pues es compartido
     */
    private BufferedReader dmonIn;

    /**
     * Este atributo permite enviar mensajes al proceso
     * DMon. Por ser un recurso compartido, es necesario
     * bloquear el atributo al modificarlo.
     */
    private PrintWriter dmonOut;

    HiloDRep(Socket socketCliente, File repositorio, boolean verboso,
            BufferedReader dmonIn, PrintWriter dmonOut){
        this.socketCliente = socketCliente;
        this.dmonIn = dmonIn;
        this.dmonOut = dmonOut;
        this.repositorio = repositorio;
        this.verboso = verboso;
        prompt = false;
    }

    HiloDRep(File repositorio, boolean verboso, BufferedReader dmonIn, 
            PrintWriter dmonOut){
        this.socketCliente = null;
        this.repositorio = repositorio;
        this.verboso = verboso;
        prompt = true;
    }

    public void run() {

        if(verboso){
            System.out.println("Se ha creado un nuevo hilo!");
            System.out.flush();
        }
   
        try{
        
            establecerES();
        
            //Esperar por comandos
            while(true){
                
                String comando = leerComando();

                //Parsear el comando. En caso de que sea un comando
                //válido, se responderá con un string aprobatorio
                //primero

                //rdir
                if( (comando.equals(Protocol.RDIR) && !prompt) ||
                    (comando.equals(Protocol.LDIR) && prompt) ){

                    File[] archivos = repositorio.listFiles();
                    if(!prompt){
                        out.println(Protocol.OK);
                        out.println(archivos.length);
                    }
                    for(int i=0;i<archivos.length;i++){
                        out.println(archivos[i].getName() + "\t" +
                                archivos[i].length()+"B");
                    }
                    out.flush();
                }

                //sube (sólo posible desde cliente remoto)
                else if( comando.equals(Protocol.SUBE) ){

                    synchronized(dmonOut){
                        dmonOut.println(ProtocoloDMon.UP);
                    }
                    synchronized(dmonIn){
                        String resp = dmonIn.readLine();
                        if(!resp.equals(ProtocoloDMon.OK))
                            throw new IOException("Error en la comuncación con DMon");
                    }

                    String nombreDeArchivo = in.readLine();

                    long numDeBytes = Long.parseLong(in.readLine());

                    if(numDeBytes > Integer.MAX_VALUE/2){
                        out.println(Protocol.ERROR_INESPERADO);
                        throw new IOException("Se intentó subir un "+
                                "archivo muy pesado");
                    }
                    
                    //byte[] archivo = new byte[(int)numDeBytes];

                    //for(int i=0;i<(int)numDeBytes;i++)
                    //    archivo[i] = in.nextByte();
                    char[] streamDeChars = new char[(int)numDeBytes];

                    in.read(streamDeChars, 0, (int)numDeBytes);

                    PrintStream fout = 
                        new PrintStream( new FileOutputStream(
                            new File(repositorio.getName()+
                                "/"+nombreDeArchivo)));

                    //fout.write(streamDeChars,0,(int)numDeBytes);
                    fout.print(streamDeChars);
                    fout.close();

                    out.println(Protocol.OK);
                    out.flush();
                    //TODO probar con binarios! o cosas no formateadas! como un .tar.gz!
                }
                
                //baja (sólo posible desde cliente remoto)
                else if( comando.equals(Protocol.BAJA)){

                    synchronized(dmonOut){
                        dmonOut.println(ProtocoloDMon.DOWN);
                    }
                    synchronized(dmonIn){
                        String resp = dmonIn.readLine();
                        if(!resp.equals(ProtocoloDMon.OK))
                            throw new IOException("Error en la comuncación con DMon");
                    }

                    String nombreDeArchivo = in.readLine();
                    leerYEscribirArchivo(nombreDeArchivo);

                }
                //tuser (sólo posible desde cliente remoto)
                else if( comando.equals(Protocol.TUSER)){

                    synchronized(dmonOut){
                        dmonOut.println(ProtocoloDMon.TUSER);
                    }
                    synchronized(dmonIn){
                        String resp = dmonIn.readLine();
                        if(!resp.equals(ProtocoloDMon.OK))
                            throw new IOException("Error en la comuncación con DMon");

                        out.println(Protocol.OK);
                        int j = 0;
                        int m = Integer.parseInt(dmonIn.readLine());
                        out.println(m);
                        while(j<m){
                            out.println(dmonIn.readLine());
                            j++;
                        }
                    }
                }
                //nrep (sólo posible desde cliente remoto)
                else if( comando.equals(Protocol.NREP)){

                    synchronized(dmonOut){
                        dmonOut.println(ProtocoloDMon.NREP);
                    }
                    synchronized(dmonIn){
                        String resp = dmonIn.readLine();
                        if(!resp.equals(ProtocoloDMon.OK))
                            throw new IOException("Error en la comuncación con DMon");

                        out.println(Protocol.OK);
                        int j = 0;
                        int m = Integer.parseInt(dmonIn.readLine());
                        out.println(m);
                        while(j<m){
                            out.println(dmonIn.readLine());
                            j++;
                        }
                    }
                }
                //salir
                else if( comando.equals(Protocol.SALIR) ){
                    break;
                }

                //error de protocolo
                else {
                    if(prompt){
                        out.println("Comando no permitido, ejecute el "+
                                "comando 'ayuda' para una lista "+
                                "detallada de comandos permitidos");
                        out.flush();
                    }
                    else {
                        out.println(Protocol.ERROR_DE_SINTAXIS);
                        out.flush();
                        throw new IOException("Error de protocolo, "+
                                "comando no reconocido"); //TODO
                    }
                }

            }
            in.close();

        } catch (NumberFormatException nfe) {
            
            out.println(Protocol.ERROR_DE_SINTAXIS);

        } catch (IOException e) {
        
            out.println(Protocol.ERROR_INESPERADO);
            System.err.println("Error leyendo/escribiendo");
            System.err.println(e);
        
        } finally {

            out.close();
        
        }

    }

    /**
     * Este procedimiento establece el valor de las variables
     * de entrada y salida dependiento si la E/S se realizará
     * por cónsola o por un socket
     */
    private void establecerES() throws IOException {
        if(!prompt){
            out = new PrintStream(socketCliente.getOutputStream(), true);
            in = new BufferedReader(
                    new InputStreamReader(
                        socketCliente.getInputStream()));//,"US-ASCII"));
            //in = new Scanner(socketCliente.getInputStream());

            //Ahora esperamos por el comando de iniciar protocolo
            //para validar la conexión
            String inicio = in.readLine();
            String uname = in.readLine();

            if(!inicio.equals(Protocol.CONNECT) ||
                    uname == null || uname.length()==0){
                throw new IOException("Error de protocolo");
            }
            else {

                synchronized(dmonOut){
                    dmonOut.println(ProtocoloDMon.NEWUSER);
                    dmonOut.println(uname);
                    dmonOut.flush();
                }
                synchronized(dmonIn){
                    String resp = dmonIn.readLine();
                    if(!resp.equals(ProtocoloDMon.OK))
                        throw new IOException("Error en la comuncación con DMon");
                }
                out.println(Protocol.OK);
                out.flush();
            }

            if(verboso){
                System.out.println("Se conectó el usuario: "+uname);
                System.out.flush();
            }

        } else {

            out = System.out;
            //in = new Scanner(System.in);
            in = new BufferedReader(new InputStreamReader(System.in));//,"US-ASCII"));

            out.println( "Ingrese el comando 'ayuda' (sin comillas) "+
                    "para información detallada");
            out.flush();
        }
    }

    /**
     * Esta función lee un comando, bien sea del socket o
     * o de la línea de comandos. En caso de haber un error
     * de protocolo, se reporta el error.
     *
     * @return  Si el comando fue leído exitosamente, se retorna
     *          dicho comando. En caso contrario, null es retornado.
     */
    private String leerComando() throws IOException {

        if(prompt){
            out.print("drep> ");
            out.flush();
        }

        String comando = in.readLine();

        if(verboso){
            System.out.println("Recibí el comando: "+comando);
            System.out.flush();
        }

        if(comando==null)
            throw new IOException(
                    "La conexión se ha cerrado "+
                    "inesperadamente");

        if(!prompt){
            return comando;
        }

        comando = comando.trim();

        if(comando.length()==0)
            return leerComando();
        else if(comando.equals("ayuda")){
            out.println(
                "Ingrese el comando que desee que sea ejecutado, los "+
                "comandos reconocidos son los siguientes:"
            );
            out.println();
            out.println(
                "\tayuda:    Imprime este menú"        
            );
            out.println();
            out.println(
                "\tldir:     Se listarán los nombres y tamaños de cada "+
                "uno de los archivos pertenecientes al directorio "+
                "repositorio"        
            );
            out.println();
            out.println(
                "\tcat <nomb_archiv>: Lista por pantalla el contenido "+
                "del archivo local <nomb_archiv> que debe estar en "+
                "el repositorio"
            );
            out.println();
            out.println(
                "\tsalir:    Terminar el programa y todos sus hilos"
            );

            out.flush();
            return leerComando();
        }
        else if(comando.startsWith("cat ")){
            String nombreDeArchivo = comando.substring(3);
            nombreDeArchivo = nombreDeArchivo.trim();
            leerYEscribirArchivo(nombreDeArchivo);

            out.flush();
            return leerComando();
        }

        return "@"+comando;
    }


    /**
     * Esta función abre y lee un archivo y sus contenidos los
     * escribe por el PrintWriter almacenado en el atributo de la
     * clase 'out'
     * @param nombreDeArchivo El nombre del archivo a abrir
     */
    void leerYEscribirArchivo(String nombreDeArchivo) throws IOException {
        File archivo = new File(repositorio.getName()+"/"
                +nombreDeArchivo);

        if(!archivo.exists() || !archivo.isFile()){
            out.println(Protocol.ARCHIVO_NO_ENCONTRADO);
            throw new IOException("Error de protocolo"); //TODO
        }

        //Si el archivo es muy grande, abortar
        if( archivo.length() > Integer.MAX_VALUE/2 ){
            out.println(Protocol.ERROR_INESPERADO);
            throw new IOException("Archivo muy grande! "
                    +nombreDeArchivo);
        }

        char[] streamDeChars = new char[(int)archivo.length()];

        //FileInputStream fis = new FileInputStream(archivo);
        //Ffis.read(streamDeChars, 0, (int)archivo.length());

        InputStreamReader isr = 
            new InputStreamReader(new FileInputStream(archivo));//,"US-ASCII");
        isr.read(streamDeChars, 0, (int)archivo.length());

        if(!prompt){
            out.println(Protocol.OK);
            out.println(archivo.length());
        }

        //out.write(streamDeChars,0,(int)archivo.length());
        out.print(streamDeChars);

        out.flush();
        isr.close();
    }
}


